module XMLModel
  GARBAGE = '^A-Za-z0-9_\-.,'
  GARBAGE_MSG = "! Warning every character not including A-Za-z0-9_-., will be removed."
  class Document
    def initialize(declaration = Declaration.new, root = Element.new)
      @declaration = declaration
      @root = root
      @cursor = root
    end

    def get_declaration
      @declaration
    end

    def get_cursor
      @cursor
    end

    def set_cursor(node)
      @cursor = node
    end

    def get_root
      @root
    end

    def move_cursor_forward
      if @cursor.is_a?(ParentNode) && @cursor.has_child?
          @cursor = @cursor.get_first_child
      else
        if @cursor.next_sibling
          @cursor = @cursor.next_sibling
        else
          while( @cursor.has_parent? && @cursor.get_parent.get_last_child == @cursor)
            @cursor = @cursor.get_parent
          end
          if @cursor.next_sibling
            @cursor = @cursor.next_sibling
          end
        end
      end
    end

    def move_cursor_backward
      if @cursor.has_parent?
        if @cursor.previous_sibling
          # go to the rightmost subnode of the previous sibling node
          @cursor = @cursor.previous_sibling
          while @cursor.is_a?(ParentNode) && @cursor.has_child?
            @cursor = @cursor.get_last_child
          end
        else
          @cursor = @cursor.get_parent
        end
      else # if cursor is pointing at root node
        # go to the rightmost subnode of the xml tree
        while @cursor.is_a?(ParentNode) && @cursor.has_child?
          @cursor = @cursor.get_last_child
        end
      end
    end

    def move_cursor_to_parent
      if @cursor.has_parent?
        @cursor = @cursor.get_parent
      end
    end

    def move_cursor_to_first_child
      if @cursor.is_a?(ParentNode) && @cursor.has_child?
        @cursor = @cursor.get_first_child
      end
    end

    def move_cursor_to_last_child
      if @cursor.is_a?(ParentNode) && @cursor.has_child?
        @cursor = @cursor.get_last_child
      end
    end

    def to_pretty_xml
      xml = ""
      xml += "#{@declaration.to_xml}"
      xml += "\n"
      xml += "#{@root.to_xml}"
      xml
    end

    def to_xml_with_cursor
      xml = ""
      xml += "    #{@declaration.to_xml}"
      xml += "\n"
      xml += "#{@root.to_xml(2,@cursor)}"
      xml
    end

    def to_s
      "[Document]"
    end
  end

  class Node
    def initialize
      @parent
    end

    def get_parent
      @parent
    end

    def has_parent?
      not @parent.nil?
    end

    def is_root?
      @parent.nil?
    end

    def set_parent(node)
      @parent = node
    end

    def insert_after(new_node)
      current_position = @parent.get_child_position(self)
      @parent.insert_child(new_node,current_position + 1)
    end

    def insert_before(new_node)
      current_position = @parent.get_child_position(self)
      @parent.insert_child(new_node,current_position)
    end

    def next_sibling
      return nil if @parent.nil?
      return nil if @parent.get_last_child == self

      current_position = @parent.get_child_position(self)
      @parent.get_child_at(current_position + 1)
    end

    def previous_sibling
      return nil if @parent.nil?
      return nil if @parent.get_first_child == self

      current_position = @parent.get_child_position(self)
      @parent.get_child_at(current_position - 1)
    end

    def edit
      raise NotImplementedError
    end
  end

  class ParentNode < Node
    def initialize
      super
      @children = []
    end

    def has_child?
      not @children.empty?
    end

    def get_first_child
      @children.first
    end

    def get_last_child
      @children.last
    end

    def get_child_at(position)
      @children[position]
    end

    def insert_child(new_node, position = -1)
      new_node.set_parent(self)
      @children.insert(position,new_node)
    end

    def get_child_position(child_node)
      @children.index(child_node)
    end

    def delete_child(child_node)
      @children.delete(child_node)
    end

    def delete
      @children.each do |child|
        child.delete
      end
      @children = []
      get_parent.delete_child(self)
      set_parent(nil)
    end
  end

  class LeafNode < Node
    def initialize
      super
    end

    def delete
      get_parent.delete_child(self)
      set_parent(nil)
    end
  end

  class Element < ParentNode
    def initialize(name = "element")
      super()
      @name = name
      @attributes = {}
    end

    def get_attributes
      @attributes
    end

    def set_attributes(attributes)
      @attributes = attributes
    end

    def get_attribute(key)
      @attributes[key]
    end

    def set_attribute(key,value)
      @attributes[key] = value
    end

    def get_name
      @name
    end

    def set_name(name)
      @name = name.tr(GARBAGE, '')
    end

    def to_xml(depth = 0, cursor = nil, complete = true)
      xml = ""

      if self == cursor
        xml += "~~> "
        xml += "  " * (depth - 2)
      else
        xml += "  " * depth
      end

      xml += "<#{@name}"
      for key in @attributes.keys
        xml += " #{key}=\"#{@attributes[key]}\""
      end
      if @children.empty?
        xml += "/>"
      else
        xml += ">"
        xml += "\n"
        if complete
          for child in @children
            xml += "#{child.to_xml(depth + 1,cursor)}"
            xml += "\n"
          end
        else
          xml += "  " * ( depth + 1 )
          xml += " ... "
          xml += "\n"
        end
        xml += "  " * depth
        xml += "</#{@name}>"
      end

      xml
    end

    def to_s
      "[Element name:\"#{@name}\" attributes:#{@attributes}]"
    end

    def edit
      puts ""
      puts "~ current content of the element node:"
      puts to_xml(0,nil,false)
      puts ""

      i = 1
      puts "(0) element name: #{get_name}"
      @attributes.each do |key,value|
        puts "(#{i}) #{key}=\"#{value}\""
        i += 1
      end
      puts "(n) insert new attribute"

      print "! Choose what to edit or delete (0..#{@attributes.size}): "

      answ = gets.chomp

      case answ
      when "0"
        puts GARBAGE_MSG
        print "~ Enter the new element name (#{get_name}): "
        new_name = gets.chomp

        set_name(new_name)
      when "1"..(@attributes.size.to_s)
        key = @attributes.keys[(answ.to_i) - 1]
        puts "~ selected attribute: #{key}=\"#{@attributes[key]}\""

        puts GARBAGE_MSG
        print "~ Enter the updated attribute key (empty string to delete): "

        new_key = gets.chomp

        p "||#{new_key}||"

        if new_key =~ /^\s*$/
          @attributes.delete(key)
        else
          @attributes[new_key] = @attributes.delete(key)

          puts GARBAGE_MSG
          print "~ Enter the updated attribute value: "
          new_value = gets.chomp

          @attributes[new_key] = new_value
        end
      when "n"
        puts GARBAGE_MSG
        print "~ Enter the new attribute key (existing keys will be overwritten): "
        new_key = gets.chomp
        puts GARBAGE_MSG
        print "~ Enter the new attribute value: "
        new_value = gets.chomp

        @attributes[new_key] = new_value
      else
        puts "! The selection is invalid. Please try again!"
        edit
      end
      puts ""
      puts "~ updated content of the element node:"
      puts to_xml(0,nil,false)
      puts ""
    end
  end

  class Declaration
    def initialize(encoding = 'utf-8', version = '1.0')
      @encoding = encoding
      @version = version
    end

    def get_encoding
      @encoding
    end

    def set_encoding(encoding)
      @encoding = encoding.tr(GARBAGE, '')
    end

    def get_version
      @version
    end

    def set_version(version)
      @version = version.tr(GARBAGE, '')
    end

    def to_xml(depth = 0, cursor = nil, complete = true)
      xml = ""
      if self == cursor
        xml += "~~> "
        xml += "  " * (depth - 2)
      else
        xml += "  " * depth
      end
      xml += "<?xml version=\"#{@version}\" encoding=\"#{@encoding}\"?>"
      xml
    end

    def to_s
      "[Declaration version:'#{@version}' encoding:'#{@encoding}']"
    end
  end

  class Comment < LeafNode
    def initialize(comment_text = "commenttext")
      super()
      @comment_text = comment_text
    end

    def get_comment_text
      @comment_text
    end

    def set_comment_text(comment_text)
      @comment_text = comment_text.tr(GARBAGE, '')
    end

    def to_xml(depth = 0, cursor = nil, complete = true)
      xml = ""
      if self == cursor
        xml += "~~> "
        xml += "  " * (depth - 2)
      else
        xml += "  " * depth
      end
      xml += "<!--#{@comment_text}-->"
      xml
    end

    def to_s
      "[Comment text:'#{@comment_text}']"
    end

    def edit
      puts ""
      puts "~ current content of the comment node:"
      puts to_xml(0,nil,false)
      puts ""
      puts GARBAGE_MSG
      print "~ Please enter new content: "

      answ = gets.chomp

      set_comment_text(answ)

      puts ""
      puts "~ comment node successfully changed!"
      puts "~ updated content of the comment node:"
      puts to_xml(0,nil,false)
    end
  end

  class PlainText < LeafNode
    def initialize(text = "plain text")
      super()
      @text = text
    end

    def get_text
      @text
    end

    def set_text(text)
      @text = text.tr(GARBAGE, '')
    end

    def to_xml(depth = 0, cursor = nil, complete = true)
      xml = ""
      if self == cursor
        xml += "~~> "
        xml += "  " * (depth - 2)
      else
        xml += "  " * depth
      end
      xml += "#{@text}"
      xml
    end

    def to_s
      "[Plaintext text:'#{@text}']"
    end

    def edit
      puts ""
      puts "~ current content of plain text node:"
      puts to_xml(0,nil,false)
      puts ""
      puts GARBAGE_MSG
      print "~ Please enter new content: "

      answ = gets.chomp

      set_text(answ)

      puts ""
      puts "~ plain text node successfully changed!"
      puts "~ updated content of plain text node:"
      puts to_xml(0,nil,false)
    end
  end
end